/**
 *	The NeuroCoSA Toolkit
 *	Copyright (C) 2003-6 Stuart Meikle.
 *
 *	This is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU Lesser General Public
 *	License as published by the Free Software Foundation; either
 *	version 2.1 of the License, or (at your option) any later version.
 *
 *	This library is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *	Lesser General Public License for more details.
 *
 * @author	Stuart Meikle
 * @version	2006-halloween(mk2)
 * @license	LGPL
 */
package org.stumeikle.NeuroCoSA;
import java.util.*;
import org.stumeikle.NeuroCoSA.PubSub.TimeLord;
import org.stumeikle.NeuroCoSA.NIS.*;


/** Layer class. the lobe's of old.
 *  Contains neurons. Exists within a lobe. has an information service
 */
public class Layer implements Comparable, TimeLord
{
    LayerInfoService		iLIS;
    Cortex			iParent;
    LinkedList<Neuron>		iNeurons;
    				/** 
     				 * a list of dead neurons which need to be removed
     				 */
    Info			iNeuronsPub;
    LinkedList<Neuron>		iDeadNeurons;
    long			iNextID;
    long			iNumNeurons;
    InfoLong			iPublishedNumNeurons;

    //lobe specific stuff:
    int				iUpdateOrder;
    int				iHierarchyLevel;

    //what layers this layer connects to
    //primarily for UI/ debug info
    InfoLong			iNumOutputLayers;
    InfoLong			iNumInputLayers;
    Info			iListOutputLISs;
    Info			iListInputLISs;

    public Layer(Cortex l)
    {
	iParent = l;
	iUpdateOrder = 1;
	iHierarchyLevel = 1;
	//probably the hierarchy value needs to go into the layer info service

	iLIS = new LayerInfoService(iParent.getInfoService());
	iNeurons = new LinkedList<Neuron>();
        iNumNeurons = 0;
	iPublishedNumNeurons = new InfoLong("NumberOfNeurons", iNumNeurons);
	iLIS.addPublication(iPublishedNumNeurons);
	iDeadNeurons = new LinkedList<Neuron>();
	iNextID = 1;

	iNeuronsPub = new Info("ListOfNeurons");
	iNeuronsPub.setValue(iNeurons);
	iLIS.addPublication( iNeuronsPub );
	
	//publish the number of neurons so that other entities can respond to 
	//a change in the value
	iNumOutputLayers = new InfoLong("NumberOfOutputLayers", 0);
	iNumInputLayers = new InfoLong("NumberOfInputLayers", 0);

	iListOutputLISs = new Info("ListOfOutputLISs");
	iListOutputLISs.setValue( new LinkedList<LayerInfoService>() );
	iListInputLISs = new Info("ListOfInputLISs");
	iListInputLISs.setValue( new LinkedList<LayerInfoService>() );
    	iLIS.addPublication(iNumOutputLayers);
	iLIS.addPublication(iListOutputLISs);
	iLIS.addPublication(iNumInputLayers);
	iLIS.addPublication(iListInputLISs);
    }

    public void	getReady(){}

    public void update()
    {
	System.out.println("ERROR : layer update function should be overloaded by each specific lobe");
	System.exit(1);
    }

    public void cortexUpdate()
    {
	//called by the cortex. this is the baseclass update routine which calls the
	//specific layer implementation afterwards
	//(0) boredom processing can go in here later
	
	//(1)
	removeDeadNeurons();
	
	//(2)
	//call the specific 
	update();

	//(3) layers in the bind layer (hierarchy lowest level) can write output to the
	//cortex 
    }

    public LayerInfoService	getInfoService()
    { return iLIS; }

    public int			getHierarchyLevel()
    { return iHierarchyLevel; }

    public void			setHierarchyLevel( int i )
    { iHierarchyLevel = i; }

    public int			getUpdateOrder()
    { return iUpdateOrder; }
    
    public void			setUpdateOrder( int i )
    { iUpdateOrder = i ; }

    public LinkedList		getNeurons()
    {
	return iNeurons;
    }

    public void			addNeuron( Neuron aN ) 
    {
	iNumNeurons ++;
	iPublishedNumNeurons.setValue(iNumNeurons);
	iNeuronsPub.lock(this);	//lock the publication because the list is modded
	if (aN.getID()==0)
    	    aN.setID(iNextID++);// absolutely necessary for info service
    	iNeurons.add(aN);
	iNeuronsPub.releaseAndNotify(this);
    }
	
    public	void		removeNeuron( Neuron aN )
    {
    	if (aN!=null)
	{
	    //mark the neuron
	    aN.setState(Neuron.state_dead);
//ADD	    aN.destroyAllSynapses();
	    iDeadNeurons.add(aN);
	}
    }

    public void			resetAllNeurons()
    {
   	ListIterator	i = iNeurons.listIterator();
   	for(;i.hasNext();)
    	{
     	    Neuron	n = (Neuron)i.next();
//ADD	    n.resetSynapses();
	    n.setState( Neuron.state_inactive );
        }
    }

    private void		removeDeadNeurons()
    {
    	ListIterator	i = iDeadNeurons.listIterator();
	
	iNeuronsPub.lock(this);	//lock the publication because the list is modded
	for(;i.hasNext();)
	{
	    Neuron	n = (Neuron) i.next();

	    //not this simple! need to make sure that all the associated synapses are 
	    if (iNeurons.remove(n) == true)
	    {
		iNumNeurons--;
	    }
	}
	
	iPublishedNumNeurons.setValue(iNumNeurons);
	iNeuronsPub.releaseAndNotify(this);
	//if (iNumInstances != s)
	//	System.out.println("Removed " + (s-iNumInstances) + " neurons from lobe " + iUniqueName );
	
	iDeadNeurons.clear();
    }


    public void			updateNeurons()/// I expect this to be overloaded in bimodelayer later
    {
//ADD	//iLis.startOfLobeUpdate();
	
	ListIterator		i = iNeurons.listIterator(0);
	for(;i.hasNext();)
	{
	    Neuron		sn = (Neuron)i.next();
	    sn.update();
	
//ADD	    //iLis.instanceUpdate(sn);
	}
//ADD	//iLis.endOfLobeUpdate();
    }

    //proxies for information
    public long			getBrainTime()
    { return getInfoService().getBrainTime(); }

    //timelord interface
    public long			getTime()
    { return getInfoService().getBrainTime(); }

    //needed for sorting
    public	int		compareTo( Object o )
    {
	Layer	c = (Layer) o;

	if (getHierarchyLevel() < c.getHierarchyLevel())	return -1;
	if (getHierarchyLevel() > c.getHierarchyLevel()) 	return 1;

	return 0;
    }  

    public 	LayerInfoService findBindLayerInfoServiceFromCortex(String n)
    {
	//(1) locate the named cortex
	//(2) find the bind layer
	//(3) get its info service
	  return iParent.findBindLayerInfoServiceFromCortex(n);
    }

    public	Cortex		getCortex()
    { return iParent; }

    /** store the given datablock at the cortex dpu output
     *  . Only the lowest layers in the hierarchy can do this
     */
    public	void		setCortexOutput(DataBlock d)
    {
	if (getHierarchyLevel() == 1) //bind layer
	{
	    getCortex().setOutput( d );
System.out.println("Layer, setting cortex output = " + d);
	}
	else
	{
	    System.out.println("ERROR: only the lowest layer within a cortex can create the cortex output");
	}
    }

    public	LinkedList<DPU>	getCortexInputDPUs()
    {
	return getCortex().getInputs();
    }

    public	void		addOutputLayer( Layer ly )
    {
	long	l = iNumOutputLayers.getLongValue();
	l++;
	iNumOutputLayers.setValue(l);


	LinkedList<LayerInfoService>	o = (LinkedList<LayerInfoService>)iListOutputLISs.getValue();
	iListOutputLISs.lock(this);
	o.add(ly.getInfoService());
	iListOutputLISs.releaseAndNotify(this);	
    }

    public	void		addInputLayer( Layer ly )
    {
	long	l = iNumInputLayers.getLongValue();
	l++;
	iNumInputLayers.setValue(l);

	LinkedList<LayerInfoService>	o = (LinkedList<LayerInfoService>)iListInputLISs.getValue();
	iListInputLISs.lock(this);
	o.add(ly.getInfoService());
	iListInputLISs.releaseAndNotify(this);	
    }
}

